package dk.itu.mario.level;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Random;
import dk.itu.mario.MarioInterface.GamePlay;
import dk.itu.mario.MarioInterface.LevelInterface;
import dk.itu.mario.engine.sprites.SpriteTemplate;
import dk.itu.mario.engine.sprites.Enemy;


public class MyLevel extends Level{
	//Store information about the level
	public   int ENEMIES = 0; //the number of enemies the level contains
	public   int BLOCKS_EMPTY = 0; // the number of empty blocks
	public   int BLOCKS_COINS = 0; // the number of coin blocks
	public   int BLOCKS_POWER = 0; // the number of power blocks
	public   int COINS = 0; //These are the coins in boxes that Mario collect
	
	private HashMap<ChunkType,Double> odds = new HashMap<ChunkType,Double>();
	private HashMap<ChunkType,Double> highOdds = new HashMap<ChunkType,Double>();
	private HashMap<ChunkType,Double> lowOdds = new HashMap<ChunkType,Double>();
	private double totalOdds = 0;
	private double totalHighOdds = 0;
	private double totalLowOdds = 0;
	
	private double killerMetric = 0;
	private double collectorMetric = 0;
	private double runnerMetric = 0;
	
	private static Random levelSeedRandom = new Random();
	public static long lastSeed;
	
	Random random;
	Random itemRandom;
	Random enemyRandom;
	
	
	private int difficulty;
	private int type;
	private int gaps;
	
	
	private GamePlay metrics;
	private double idealPace;
	
	private final boolean CHUNKDEBUG = false;
	
	private final boolean DEBUGGENTLE = false;
	private final boolean DEBUGSTANDARD = false;
	private final boolean DEBUGARMY = false;
	private final boolean DEBUGKOOPAKILL = false;
	private final boolean DEBUGDEATHPIT = false;
	private final boolean DEBUGCOMB = false;
	private final boolean DEBUGTUBECOMB = false;
	private final boolean DEBUGWALL = false;
	private final boolean DEBUGGAP = false;
	private final boolean DEBUGAIR = false;
	private final boolean DEBUGCANNONMOUNTAIN = false;
	
	private boolean[] playerType = {false,false,false};//killer, runner, collector
	
	
	public MyLevel(int width, int height) {
		super(width, height);
	}
	
	public MyLevel(int width, int height, long seed, int difficulty, int type, GamePlay playerMetrics) throws Exception {
		this(width, height);
		metrics = playerMetrics;
		//printMetric();
		setMetrics();
		creat(seed, difficulty, type);
	}
	
	public void printMetric() {
		for (ChunkType i : ChunkType.values()) {
			if (metrics.chunkTypeTimes.get(i) != 0)
				System.out.println(i.name() + ": " + (double) metrics.chunkTypeTimes.get(i) / metrics.chunkTypeLengths.get(i));
		}
	}
	
	public void setMetrics() {
		ChunkType.init();
		
		int distanceTraveled = 0;
		int levelLength = 0;
		for (ChunkType i : ChunkType.values()) {
			distanceTraveled += metrics.chunkTypeLengths.get(i);
		}
		for (int i : metrics.chunkLengths) {
			levelLength += i;
		}
		
		//KILLER - #enemies killed/total # of enemies
		killerMetric = (double) (metrics.RedTurtlesKilled + metrics.GreenTurtlesKilled + metrics.ArmoredTurtlesKilled + metrics.GoombasKilled 
				+ metrics.CannonBallKilled + metrics.JumpFlowersKilled + metrics.ChompFlowersKilled) / ((double) metrics.totalEnemies * distanceTraveled / levelLength);
		//RUNNER - completionTime/predicted time
		runnerMetric = (double) metrics.timeSpentRunning / metrics.totalTime;
		//COLLECTOR - coins collected and blocks destroyed (more focus on coins)
		collectorMetric = (double) metrics.coinsCollected / ((double) metrics.totalCoins * distanceTraveled / levelLength) * .67
			+ (double) (metrics.emptyBlocksDestroyed + metrics.powerBlocksDestroyed + metrics.coinBlocksDestroyed) / 
			((double) (metrics.totalEmptyBlocks + metrics.totalpowerBlocks + metrics.totalCoinBlocks) * distanceTraveled / levelLength) * .33;
		
		//normalizing
		double total = killerMetric + runnerMetric + collectorMetric;
		killerMetric /= total;
		runnerMetric /= total;
		collectorMetric /= total;
		
		if (killerMetric > .32) {
			playerType[0] = true;
			ChunkType.setModifier(ChunkType.STANDARD, 0.2);
			ChunkType.setModifier(ChunkType.ARMY, 0.2);
			ChunkType.setModifier(ChunkType.KOOPAKILL, 0.2);
			ChunkType.setModifier(ChunkType.DEATHPIT, 0.2);
			idealPace = 0.6;
			System.out.println("Killer!");
		}
		if (runnerMetric > .32) {
			playerType[1] = true;
			ChunkType.setModifier(ChunkType.DEATHPIT, -0.4);
			idealPace = 0.5;
			System.out.println("Runner!");
		}
		if (collectorMetric > .32) {
			playerType[2] = true;
			ChunkType.setModifier(ChunkType.DEATHPIT, 0.4);
			ChunkType.setModifier(ChunkType.WALL, 0.1);
			idealPace = 0.6;
			System.out.println("Collector!");
		}
	}
	
	public void creat(long seed, int difficulty, int type) throws Exception {
		this.type = type;
		this.difficulty = difficulty;
		
		lastSeed = seed;
		random = new Random(seed);
		
		HashMap<ChunkType,Double> chunkEfficiency = new HashMap<ChunkType,Double>();;
		HashMap<ChunkType,Integer> chunkTypeTimes = metrics.chunkTypeTimes;
		HashMap<ChunkType,Integer> chunkTypeLengths = metrics.chunkTypeLengths;
		
		ArrayList<ChunkType> lowTypes = new ArrayList<ChunkType>();
		ArrayList<ChunkType> highTypes = new ArrayList<ChunkType>();
		
		int totalTime = 0;
		int totalLength = 0;
		for (ChunkType i : ChunkType.values()) {
			double chunkTime = chunkTypeTimes.get(i) * ChunkType.modifier(i);
			double chunkLength = chunkTypeLengths.get(i);
			chunkEfficiency.put(i, chunkTime / chunkLength);
			totalTime += chunkTime;
			totalLength += chunkLength;
		}
		
		final double REALPACE = (idealPace + (double) totalTime / totalLength) / 2;
		//final double IDEALPACE = 0.6;
		final double IDEALVAR = 0.2;
		double high = 200;
		double low = 0;
		ChunkType emLow = null;
		ChunkType emHigh = null;
		
		for (ChunkType i : ChunkType.values()) {
			if (i == ChunkType.CANNONMOUNTAIN) break;
			
			double prob;
			
			if (high > chunkEfficiency.get(i)) {
				high = chunkEfficiency.get(i);
				emHigh = i;
			} else if (low < chunkEfficiency.get(i)) {
				low = chunkEfficiency.get(i);
				emLow = i;
			}
			
			if (chunkEfficiency.get(i) > REALPACE) {
				prob = normalProb(chunkEfficiency.get(i),IDEALVAR,REALPACE + 0.2);
				totalLowOdds += prob;
				lowTypes.add(i);
				lowOdds.put(i, totalLowOdds - prob);
			} else if (chunkEfficiency.get(i) < REALPACE) {
				prob = normalProb(chunkEfficiency.get(i),IDEALVAR,REALPACE - 0.2);
				totalHighOdds += prob;
				highTypes.add(i);
				highOdds.put(i, totalHighOdds - prob);
			}
			
			prob = normalProb(chunkEfficiency.get(i),IDEALVAR,REALPACE);
			if (!Double.isNaN(prob)) {
				totalOdds += prob;
				odds.put(i, totalOdds - prob);
			} else {
				odds.put(i, 200.0);
			}
		}
		
		//create the start location
		int length = 0;
		length += buildGentle(0, width);
		
		double time = chunkEfficiency.get(ChunkType.GENTLE) * length;
		double pace;
		int oldlength;
		final double LOWPACE;
		final double HIGHPACE;
		
		if (REALPACE > low) {
			LOWPACE = high + 0.2;
			HIGHPACE = high - 0.4;
			double prob = normalProb(chunkEfficiency.get(emHigh),IDEALVAR,REALPACE);
			totalHighOdds += prob;
			highTypes.add(emHigh);
			highOdds.put(emHigh, totalHighOdds - prob);
		} else if (REALPACE < high) {
			LOWPACE = low + 0.4;
			HIGHPACE = low - 0.2;
			double prob = normalProb(chunkEfficiency.get(emLow),IDEALVAR,REALPACE);
			totalLowOdds += prob;
			lowTypes.add(emLow);
			lowOdds.put(emLow, totalLowOdds - prob);
		} else {
			LOWPACE = REALPACE + 0.2;
			HIGHPACE = REALPACE - 0.2;
		}
		
		//create all of the medium sections
		while (length < width - 64 - 12) {
			if (!CHUNKDEBUG) {
				oldlength = length;
				pace = time / length;
				
				double t = random.nextDouble();
				ChunkType next = ChunkType.GENTLE;
				
				if (pace < HIGHPACE) {
					for (ChunkType i : lowTypes) {
						if (lowOdds.get(i) <= t * totalLowOdds) {
							next = i;
						}
					}
				} else if (pace > LOWPACE) {
					for (ChunkType i : highTypes) {
						if (highOdds.get(i) <= t * totalHighOdds) {
							next = i;
						}
					}
				} else {
					for (ChunkType i : ChunkType.values()) {
						if (i == ChunkType.CANNONMOUNTAIN) break;
						
						if (odds.get(i) <= t * totalOdds) {
							next = i;
						}
					}
				}
				
				switch (next) {
				case GENTLE:
					length += buildGentle(length, width-length-12);
					break;
				case STANDARD:
					length += buildStandard(length, width-length-12);
					break;
				case ARMY:
					length += buildArmy(length, width-length-12);
					break;
				case KOOPAKILL:
					length += buildKoopaKill(length, width-length-12);
					break;
				case DEATHPIT:
					length += buildEnemyPit(length, width-length-12);
					break;
				case COMB:
					length += buildComb(length, width-length-12);
					break;
				case TUBECOMB:
					length += buildGentle(length, width-length-12);
					break;
				case WALL:
					length += buildGentle(length, width-length-12);
					break;
				case GAP:
					length += buildGapClimb(length, width-length-12);
					break;
				case AIR:
					length += buildAir(length, width-length-12);
					break;
				}
				
				time += chunkEfficiency.get(next) * (length - oldlength);
			} else {
				boolean debugValid = false;
				if (DEBUGGENTLE) {
					length += buildGentle(length, width-length);
					debugValid = true;
				} if (DEBUGSTANDARD) {
					length += buildStandard(length, width-length);
					debugValid = true;
				} if (DEBUGARMY) {
					length += buildArmy(length, width-length);
					debugValid = true;
				} if (DEBUGKOOPAKILL) {
					length += buildKoopaKill(length, width-length);
					debugValid = true;
				} if (DEBUGDEATHPIT) {
					length += buildEnemyPit(length, width-length);
					debugValid = true;
				} if (DEBUGCOMB) {
					length += buildComb(length, width-length);
					debugValid = true;
				} if (DEBUGTUBECOMB) {
					length += buildTubeComb(length, width-length);
					debugValid = true;
				} if (DEBUGWALL) {
					length += buildWindow(length, width-length);
					debugValid = true;
				} if (DEBUGGAP) {
					length += buildGapClimb(length, width-length);
					debugValid = true;
				} if (DEBUGAIR) {
					length += buildAir(length, width-length);
					debugValid = true;
				} if (DEBUGCANNONMOUNTAIN) {
					length += buildCannonMountain(length, width-length);
					debugValid = true;
				}
				
				if (!debugValid) {
					throw new Exception("You did not select a chunk to Debug!");
				}
			}
		}

		length += buildCannonMountain(length, width-length);
		
		//set the end piece
		int floor = height - 1 - random.nextInt(4);
		
		xExit = length + 8;
		yExit = floor;
		
		// fills the end piece
		for (int x = length; x < width; x++) {
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				}
			}
		}
		
		if (type == LevelInterface.TYPE_CASTLE || type == LevelInterface.TYPE_UNDERGROUND) {
			int ceiling = 0;
			int run = 0;
			
			for (int x = 0; x < width; x++) {
				if (run-- <= 0 && x > 4) {
					ceiling = random.nextInt(4);
					run = random.nextInt(4) + 4;
				}
				for (int y = 0; y < height; y++) {
					if ((x > 4 && y <= ceiling) || x < 1) {
						setBlock(x, y, GROUND);
					}
				}
			}
		}
		
		fixWalls();
	}
	
	
	/*----------------------------------------
	 *-CHUNKS ARE HERE. SAY HI TO THE CHUNKS!-
	 *----------------------------------------*/
	
	
	private int buildAir(int xo, int maxLength) {
		int length = 12;
		if (length > maxLength) length = maxLength;
		
		int floor = 0;
    	while(getBlock(xo-1, floor) == 0 && floor < height - 2)
    		floor++;
    	floor--;
    	
    	for (int y = 0; y < height + 1; y++) {
			if (y > floor) {
				setBlock(xo, y, GROUND);
				setBlock(xo + 1, y, GROUND);
			}
		}
    	
    	for (int i = 6; i < length - 4; i ++)
    		setBlock(xo + i, floor - 2, BLOCK_EMPTY);
		
		return length;
	}
	
	private int buildArmy(int xo, int maxLength) {
		int length = random.nextInt(10) + 10;
		if (length > maxLength) length = maxLength;
		
		int floor = height - 1 - random.nextInt(4);
		for (int x = xo; x < xo + length; x++) {
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				}
			}
		}
		
		addEnemyLine(xo + 1, xo + length - 1, floor - 1);
		
		int h = floor;
		
		boolean[] occupied = new boolean[length];
		h = h - 2 - random.nextInt(3);
		int l = random.nextInt(5) + 3;
		int xxo = random.nextInt(length - l - 2) + xo + 1;
		
		occupied[xxo - xo] = true;
		occupied[xxo - xo + l] = true;
		addEnemyLine(xxo, xxo + l, h - 1);
		decorate(xxo - 1, xxo + l + 1, h, false);
		addEnemyLine(xxo, xxo + l, h - 1);
		
		for (int x = xxo; x < xxo + l; x++) {
			for (int y = h; y < floor; y++) {
				int xx = 5;
				if (x == xxo) xx = 4;
				if (x == xxo + l - 1) xx = 6;
				
				int yy = 9;
				if (y == h) yy = 8;
				
				if (getBlock(x, y) == 0) {
					setBlock(x, y, (byte) (xx + yy * 16));
				} else {
					if (getBlock(x, y) == HILL_TOP_LEFT) setBlock(x, y, HILL_TOP_LEFT_IN);
					if (getBlock(x, y) == HILL_TOP_RIGHT) setBlock(x, y, HILL_TOP_RIGHT_IN);
				}
			}
		}
		
		return length;
	}
	
	private int buildCannonMountain(int xo, int maxLength) {
		int length = 12;
		
		int floor = height - 1;
		
		setBlock(xo, floor, GROUND);
		setBlock(xo + 13, floor, GROUND);
		
		for (int x = xo + 1; x < xo + length + 1; x++) {
			int cannonHeight = floor - (x - xo);
			
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				} else {
					if (y >= cannonHeight) {
						if (y == cannonHeight) setBlock(x, y, (byte) (14 + 0 * 16));
						else if (y == cannonHeight + 1) setBlock(x, y, (byte) (14 + 1 * 16));
						else setBlock(x, y, (byte) (14 + 2 * 16));
					}
				}
			}
		}
		
		return length + 2;
	}
	
	public int buildComb(int xo, int maxLength){
		int length = random.nextInt(10) + 3;
		int floor = height - 1 - random.nextInt(3);
		
		if (length % 3 == 2) {
			length += 1;
		}
		
		for (int x = xo; x < xo + length; x++) {
			if ((x-xo)%3 != 0){
				for (int y = 0; y < height; y++) {
					if (y >= floor) {
						setBlock(x, y, Level.GROUND);
					}
				}    
			}
		}
		
		return length;
	}
    
    private int buildEnemyPit(int xo, int maxLength)
    {
    	//Determine length of chunk
    	int length = random.nextInt(20) + 5;
    	if(length<12) length = 12;
    	if(length>maxLength) length = maxLength;
    	
    	//Determine floor height
    	int floor = 0;
    	while(getBlock(xo-1, floor) == 0 && floor < height - 1)
    		floor++;
    	floor--;
    	
    	int pitHeight = floor + random.nextInt(3);
    	
    	int enemyType = 0;
    	
        //Fill in floor
    	for(int x=xo+1; x<xo+length; x++) {
    		setBlock(x, floor+1, HILL_TOP);
    		
    		if(x>(xo+2) && x<(xo+length-4)) {
    			//Enemies
    			type = random.nextInt(15 - difficulty);
    			if(type<4)
    				setSpriteTemplate(x, floor, new SpriteTemplate(type, false));
    			
    			//Blocks
    			if( !(x == xo+length/2 || x == (xo+length/2-1))) {
    				int rand = random.nextInt(10);
    				if (rand > 8)
    					setBlock(x, floor-3, BLOCK_POWERUP);
    				else if(rand > 5)
        				setBlock(x, floor-3, BLOCK_COIN);
        			else
        				setBlock(x, floor-3, BLOCK_EMPTY);
    			}
    		}
    		
    		//Floor
    		for(int y=floor-2; y<15; y++)
    			if (y > floor) setBlock(x, y, GROUND);
    			else if (x < xo + length - 1) setBlock(x, y, COIN);
    	}
    	
    	//Fill in sides
   		for(int y=floor-2; y<15; y++) {
   	    	for(int x=0; x<3; x++) {
   		    	setBlock(xo+x, floor-3, GROUND);
   		    	setBlock(xo+x, y, GROUND);
   		   		setBlock(xo+length-4+x, floor-3, GROUND);
   		   		setBlock(xo+length-4+x, y, GROUND);
   	    	}
   		}
   		
    	return length;
    }
    
    private int buildGapClimb(int xo, int maxLength) {
    	//Determine length of chunk
    	int length = random.nextInt(20);
    	if(length<10) length = 10;
    	if(length>maxLength) length = maxLength;
    	
    	//Determine floor height
    	int floor = height - 1 - random.nextInt(4);
    	
    	//Fill in first column
   		setBlock(xo+1, floor-1, HILL_TOP_LEFT);
   		setBlock(xo+2, floor-1, HILL_TOP_RIGHT);
   		for(int y=floor; y<15; y++) {
   			setBlock(xo+1, y, HILL_LEFT);
   			setBlock(xo+2, y, HILL_RIGHT);
   		}
        
   		//Fill in columns
   		for(int i=1; i<(length-2)/4; i++) {
   			buildCliff(xo+4*i+1, 15, 2, 16-floor+2*i);
   		}
   		
    	return length;
    }
	
	private int buildGentle(int xo, int maxLength) {
		int length = 10 + random.nextInt(5);
		
		if (length > maxLength)
			length = maxLength;
		
		int floor = height - 1 - random.nextInt(4);
		
		//runs from the specified x position to the length of the segment
		for (int x = xo; x < xo + length; x++) {
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				}
			}
		}
		
		if (length > 5) {
			decorate(xo, xo + length, floor, true);
		}
		
		return length;
	}
	
	private int buildKoopaKill(int xo, int maxLength) {
		int length = 8 + random.nextInt(3);
		
		if (length + 4 > maxLength)
			length = maxLength - 4;
		
		int floor = height - 2 - random.nextInt(3);
		
		//runs from the specified x position to the length of the segment
		for (int x = xo + 2; x < xo + length; x++) {
			for (int y = 0; y < height + 1; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				}
			}
		}
		
		addKoopaLine(xo + 2, xo + length - 1, floor - 1, 3);
		
		setBlock(xo, floor - 1, GROUND);
		setBlock(xo + 1, floor - 1, GROUND);
		
		for (int y = 0; y < height + 1; y++) {
			if (y >= floor) {
				setBlock(xo, y, GROUND);
				setBlock(xo + 1, y, GROUND);
				setBlock(xo + length + 2, y, GROUND);
				setBlock(xo + length + 3, y, GROUND);
			}
		}
		
		return length + 4;
	}
	
	private int buildStandard(int xo, int maxLength) {
    	//Determine length of chunk
    	int length = random.nextInt(20);
    	if(length<8) length = 8;
    	if(length>maxLength) length = maxLength;
    	
    	//Determine floor height
    	int floor = height - 2 - random.nextInt(4);
    	
    	//Determine hill sizes/locations
    	int firstHillHeight = random.nextInt(3)+2, secondHillHeight = random.nextInt(3)+2;
    	int firstHillWidth = random.nextInt(6)+2, secondHillWidth = random.nextInt(6)+2;
    	int firstHillStart = random.nextInt(3) + xo, secondHillStart = random.nextInt(length-secondHillWidth)+xo;
    	while(firstHillHeight == secondHillHeight)
    		secondHillHeight = random.nextInt(3)+2;
    	
    	//Fill in first and last column
   		setBlock(xo, floor+1, GROUND);
   		setBlock(xo+length-1, floor+1, GROUND);
   		for(int y=floor+2; y<15; y++) {
   			setBlock(xo, y, GROUND);
   			setBlock(xo+length-1, y, GROUND);
   		}
   		
    	//Fill in hills
        for(int x=xo; x<xo+length; x++) {
        	if(x==firstHillStart)//First hill
        		buildCliff(x, floor, firstHillWidth, firstHillHeight);
        	else if (x==secondHillStart)//Second hill
        		buildCliff(x, floor, secondHillWidth, secondHillHeight);
        	else
        		for(int y=floor+1; y<16; y++)
        			setBlock(x, y, GROUND);
        	if(x>xo)
        		setBlock(x, floor+1, GROUND);
        }
    	
        //Add enemies
        addEnemyLine(xo + 1, xo + length - 1, floor - 1);
        addEnemyLine(secondHillStart+1, secondHillStart+secondHillWidth-1, floor - secondHillHeight-1);
        addEnemyLine(firstHillStart+1, firstHillStart+secondHillWidth-1, floor - firstHillHeight-1);
        
    	return length;
    }
	
	public int buildTubeComb(int xo, int maxLength){
		int length = random.nextInt(17) + 3;
		length -= (length%3);
		length++;
		
		int randomChomper = 2;//chance of chomper in tube, lower = higher chance
		int floor = height - 1 - random.nextInt(3);
		int tubeHeight = floor - 2;
		int modifier = random.nextInt(3);
		
		for (int x = xo; x < xo + length; x++) {
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				} else if ((x-xo)%3 != 0) {
					if (y >= tubeHeight - modifier) {
						if (y == tubeHeight - modifier) {
							setBlock(x, y, (byte) (((x-xo)%3 -1) + 10 + 0 * 16));
						} else {
							setBlock(x, y, (byte) (((x-xo)%3 -1) + 10 + 1 * 16));
						}
					}
				}
			}
			
			if ((x-xo)%3 == 0 && x != xo + length - 1) {
				modifier = random.nextInt(3);
				if (random.nextInt(randomChomper) == 1 && random.nextInt(2) < difficulty + 1) {
					setSpriteTemplate(x + 1, tubeHeight - modifier, new SpriteTemplate(Enemy.ENEMY_FLOWER, false));
					ENEMIES++;
				}
			}
		}
		return length;
	}
	
	public int buildWindow(int xo, int maxLength){
		int length = random.nextInt(4) + 9;
		
		int floor = height - 1 - random.nextInt(2);
		
		int windowY = floor - random.nextInt(2) - 6;
		int windowX = xo + random.nextInt(length - 8) + 4;
		
		for (int x = xo; x < xo + length; x++) {
			for (int y = 0; y < height; y++){
				if (x == windowX - 2 && (y == windowY - 5 || y == windowY - 6)){
					setBlock(x, y, COIN);
					COINS++;
				} else if (x == windowX) {
					if (y >= floor) {
						setBlock(x, y, GROUND);
					} else if (!(y == windowY || y == windowY - 1)) {
						setBlock(x, y, ROCK);
					}
				} else if (x == windowX + 2 && (y == windowY - 5 || y == windowY - 6)){
					setBlock(x, y, COIN);
					COINS++;
				} else if (x == windowX - 2 || x == windowX - 1 || x == windowX || x == windowX + 1 || x == windowX + 2){
					if (y >= floor) {
						setBlock(x, y, GROUND);
					}
				} else {
					if (y >= floor - 3) {
						setBlock(x, y, GROUND);
					}
				}
			}
		}
		return length;
	}
	
	/*------------------------------------------------------------------------------------------------
	 *-THESE ARE CHUNK HELPER METHODS. LETS CALL THEM MINION METHODS. THEY ALL PULL THE WRONG LEVERS.-
	 *------------------------------------------------------------------------------------------------*/
	
    
	private void addEnemyLine(int x0, int x1, int y) {
		boolean placement[] = new boolean[x1 - x0];
		Arrays.fill(placement, true);
		
		int minEnemies = 1;
		
		while (minEnemies > 0 && x0 < x1) {
			for (int x = x0; x < x1; x++) {
				if (random.nextInt(10) < difficulty + 1 && placement[x1 - x - 1]) {
					placement[x1 - x - 1] = false;
					int type = random.nextInt(4);
					
					if (difficulty < 1) {
						type = Enemy.ENEMY_GOOMBA;
					}
					else if (difficulty < 3) {
						type = random.nextInt(3);
					}
					
					setSpriteTemplate(x, y, new SpriteTemplate(type, random.nextInt(35) < difficulty));
					ENEMIES++;
					minEnemies--;
				}
			}
		}
	}
	
	private void addKoopaLine(int x0, int x1, int y, int min) {
		boolean placement[] = new boolean[x1 - x0];
		Arrays.fill(placement, true);
		
		while (min > 0) {
			for (int x = x0; x < x1; x++) {
				if (random.nextInt(2) < 1 && placement[x1 - x - 1]) {
					placement[x1 - x - 1] = false;
					setSpriteTemplate(x, y, new SpriteTemplate(Enemy.ENEMY_RED_KOOPA, false));
					ENEMIES++;
					min--;
				}
			}
		}
	}
	
	//Builds a cliff starting at (xo,yo) of width xx and height yy
    void buildCliff(int xo, int yo, int xx, int yy) {
    	if(xx<1) xx = random.nextInt(6)+2;
    	if(yy<1) yy = random.nextInt(6)+2;
    	
    	for(int x=xo; x<xo+xx; x++) {
    		if(x==xo) { //Left side
    			for(int y=0; y<16; y++) {
    				if(y>yo-yy) {
    					if(y<=yo)
    						setBlock(x, y, HILL_LEFT);
    					else
    						setBlock(x, y, GROUND);
    				} else if(y==yo-yy) {
    					setBlock(x, y, HILL_TOP_LEFT);
    				}
    			}
    		} else if(x==xo+xx-1) { //Right side
    			for(int y=0; y<16; y++) {
    				if(y>yo-yy) {
    					if(y<=yo)
    						setBlock(x, y, HILL_RIGHT);
    					else
    						setBlock(x, y, GROUND);
    				} else if(y==yo-yy) {
    					setBlock(x, y, HILL_TOP_RIGHT);
    				}
    			}
    		} else { //Middle
    			for(int y=0; y<16; y++) {
    				if(y>yo-yy)
    					setBlock(x, y, HILL_FILL);
    				else if(y==yo-yy)
    					setBlock(x, y, HILL_TOP);
    			}
    		}
    	}
    }
	
	private void decorate(int xStart, int xLength, int floor, boolean control) {
		//if its at the very top, just return
		if (floor < 1)
			return;
		
		//boolean coins = random.nextInt(3) == 0;
		boolean rocks = true;
		boolean hasPwr = false;
		
		//add an enemy line above the box
		//addEnemyLine(xStart + 1, xLength - 1, floor - 1);
		
		int s = random.nextInt(4);
		int e = random.nextInt(4);
		
		if (floor - 2 > 0){
			if ((xLength - 1 - e) - (xStart + 1 + s) > 1){
				for(int x = xStart + 1 + s; x < xLength - 1 - e; x++){
					setBlock(x, floor - 2, COIN);
					COINS++;
				}
			}
		}
		
		s = random.nextInt(4);
		e = random.nextInt(4);
		
		//this fills the set of blocks and the hidden objects inside them
		if (floor - 4 > 0) {
			if ((xLength - 1 - e) - (xStart + 1 + s) > 2) {
				for (int x = xStart + 1 + s; x < xLength - 1 - e; x++) {
					if (rocks) {
						if (x != xStart + 1 && x != xLength - 2 && random.nextInt(3) == 0) {
							if (random.nextInt(4) == 0 && !(control && hasPwr)) {
								setBlock(x, floor - 4, BLOCK_POWERUP);
								BLOCKS_POWER++;
								hasPwr = true;
							} else {
								//the fills a block with a hidden coin
								setBlock(x, floor - 4, BLOCK_COIN);
								BLOCKS_COINS++;
							}
						} else if (random.nextInt(4) == 0) {
							if (random.nextInt(4) == 0) {
								setBlock(x, floor - 4, (byte) (2 + 1 * 16));
							} else {
								setBlock(x, floor - 4, (byte) (1 + 1 * 16));
							}
						} else {
							setBlock(x, floor - 4, BLOCK_EMPTY);
							BLOCKS_EMPTY++;
						}
					}
				}
			}
			
			if (control && !hasPwr) {
				setBlock(xStart + 1 + s + random.nextInt((xLength - 1 - e)
						- (xStart + 1 + s)), floor - 4, BLOCK_POWERUP);
			}
		}
	}
    
    
    /*----------------------------------------------------------------------------------------------
	 *-NON CHUNK RELATED GARBAGE GOES DOWN HERE. NON CHUNK RELATED RECYCLING GOES IN THE OTHER BIN.-
	 *----------------------------------------------------------------------------------------------*/
	
	
	private void blockify(Level level, boolean[][] blocks, int width, int height){
		int to = 0;
		if (type == LevelInterface.TYPE_CASTLE) {
			to = 4 * 2;
		} else if (type == LevelInterface.TYPE_UNDERGROUND) {
			to = 4 * 3;
		}
		
		boolean[][] b = new boolean[2][2];
		
		for (int x = 0; x < width; x++) {
			for (int y = 0; y < height; y++) {
				for (int xx = x; xx <= x + 1; xx++) {
					for (int yy = y; yy <= y + 1; yy++) {
						int _xx = xx;
						int _yy = yy;
						
						if (_xx < 0) _xx = 0;
						if (_yy < 0) _yy = 0;
						if (_xx > width - 1) _xx = width - 1;
						if (_yy > height - 1) _yy = height - 1;
						
						b[xx - x][yy - y] = blocks[_xx][_yy];
					}
				}
				
				if (b[0][0] == b[1][0] && b[0][1] == b[1][1]) {
					if (b[0][0] == b[0][1]) {
						if (b[0][0]) {
							level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
						} else {
							// KEEP OLD BLOCK!
						}
					} else {
						if (b[0][0]) {
							//down grass top?
							level.setBlock(x, y, (byte) (1 + 10 * 16 + to));
						} else {
							//up grass top
							level.setBlock(x, y, (byte) (1 + 8 * 16 + to));
						}
					}
				} else if (b[0][0] == b[0][1] && b[1][0] == b[1][1]) {
					if (b[0][0]) {
						//right grass top
						level.setBlock(x, y, (byte) (2 + 9 * 16 + to));
					} else {
						//left grass top
						level.setBlock(x, y, (byte) (0 + 9 * 16 + to));
					}
				} else if (b[0][0] == b[1][1] && b[0][1] == b[1][0]) {
					level.setBlock(x, y, (byte) (1 + 9 * 16 + to));
				} else if (b[0][0] == b[1][0]) {
					if (b[0][0]) {
						if (b[0][1]) {
							level.setBlock(x, y, (byte) (3 + 10 * 16 + to));
						} else {
							level.setBlock(x, y, (byte) (3 + 11 * 16 + to));
						}
					} else {
						if (b[0][1]) {
							//right up grass top
							level.setBlock(x, y, (byte) (2 + 8 * 16 + to));
						} else {
							//left up grass top
							level.setBlock(x, y, (byte) (0 + 8 * 16 + to));
						}
					}
				} else if (b[0][1] == b[1][1]) {
					if (b[0][1]) {
						if (b[0][0]) {
							//left pocket grass
							level.setBlock(x, y, (byte) (3 + 9 * 16 + to));
						} else {
							//right pocket grass
							level.setBlock(x, y, (byte) (3 + 8 * 16 + to));
						}
					} else {
						if (b[0][0]) {
							level.setBlock(x, y, (byte) (2 + 10 * 16 + to));
						} else {
							level.setBlock(x, y, (byte) (0 + 10 * 16 + to));
						}
					}
				} else {
					level.setBlock(x, y, (byte) (0 + 1 * 16 + to));
				}
			}
		}
	}
	
	private void fixWalls() {
		boolean[][] blockMap = new boolean[width + 1][height + 1];
		
		for (int x = 0; x < width + 1; x++) {
			for (int y = 0; y < height + 1; y++) {
				int blocks = 0;
				
				for (int xx = x - 1; xx < x + 1; xx++) {
					for (int yy = y - 1; yy < y + 1; yy++) {
						if (getBlockCapped(xx, yy) == GROUND){
							blocks++;
						}
					}
				}
				blockMap[x][y] = blocks == 4;
			}
		}
		
		blockify(this, blockMap, width + 1, height + 1);
	}
    
    //Returns a pseudorandom number from a normal distribution with mean of mean and variance of var
	public double normal(double var, double mean) {
		double v1, v2, s;
		double stdDev = Math.sqrt(var);
		
        do {
        	v1 = (2 * levelSeedRandom.nextDouble() - 1);   // between -1 and 1
        	v2 = (2 * levelSeedRandom.nextDouble() - 1);//2 * levelSeedRandom.nextDouble() + mu - stdDev;   // between 
        	s = v1 * v1 + v2 * v2;
        } while (s >= 1);
        
        double multiplier = Math.sqrt(-2 * Math.log(s)/s);
        
        return (v1 * multiplier * stdDev) + mean;
	}
	
	public double normalProb(double x, double var, double mean) {
		double stdDev = Math.sqrt(var);
		return Math.exp(-Math.pow(x - mean, 2) / (2 * var)) / (stdDev * Math.sqrt(2 * Math.PI));
	}
	
	
	/*-----------------------------------------------------------------------------
	 *-OBSOLETE LEGACY METHODS GO HERE. LETS CALL THIS THE "REALLY LOW ART MUSEUM"-
	 *-----------------------------------------------------------------------------*/
	
	
	private int buildCannons(int xo, int maxLength) {
		int length = random.nextInt(10) + 2;
		if (length > maxLength) length = maxLength;
		
		int floor = height - 1 - random.nextInt(4);
		int xCannon = xo + 1 + random.nextInt(4);
		
		for (int x = xo; x < xo + length; x++) {
			if (x > xCannon) {
				xCannon += 2 + random.nextInt(4);
			}
			
			if (xCannon == xo + length - 1) {
				xCannon += 10;
			}
			
			int cannonHeight = floor - random.nextInt(4) - 1;
			
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				} else {
					if (x == xCannon && y >= cannonHeight) {
						if (y == cannonHeight) {
							setBlock(x, y, (byte) (14 + 0 * 16));
						} else if (y == cannonHeight + 1) {
							setBlock(x, y, (byte) (14 + 1 * 16));
						} else {
							setBlock(x, y, (byte) (14 + 2 * 16));
						}
					}
				}
			}
		}
		
		return length;
	}
	
	private int buildHillStraight(int xo, int maxLength) {
		int length = random.nextInt(10) + 10;
		if (length > maxLength) length = maxLength;
		
		int floor = height - 1 - random.nextInt(4);
		
		for (int x = xo; x < xo + length; x++) {
			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y, GROUND);
				}
			}
		}
		
		addEnemyLine(xo + 1, xo + length - 1, floor - 1);
		
		int h = floor;
		
		boolean keepGoing = true;
		
		boolean[] occupied = new boolean[length];
		while (keepGoing) {
			h = h - 2 - random.nextInt(3);
			
			if (h <= 0) {
				keepGoing = false;
			} else {
				int l = random.nextInt(5) + 3;
				int xxo = random.nextInt(length - l - 2) + xo + 1;
				
				if (occupied[xxo - xo] || occupied[xxo - xo + l] || occupied[xxo - xo - 1] || occupied[xxo - xo + l + 1]) {
					keepGoing = false;
				} else {
					occupied[xxo - xo] = true;
					occupied[xxo - xo + l] = true;
					
					addEnemyLine(xxo, xxo + l, h - 1);
					
					if (random.nextInt(4) == 0) {
						decorate(xxo - 1, xxo + l + 1, h, false);
						keepGoing = false;
					}
					
					for (int x = xxo; x < xxo + l; x++) {
						for (int y = h; y < floor; y++) {
							int xx = 5;
							
							if (x == xxo) xx = 4;
							if (x == xxo + l - 1) xx = 6;
							
							int yy = 9;
							if (y == h) yy = 8;
							
							if (getBlock(x, y) == 0) {
								setBlock(x, y, (byte) (xx + yy * 16));
							} else {
								if (getBlock(x, y) == HILL_TOP_LEFT) setBlock(x, y, HILL_TOP_LEFT_IN);
								if (getBlock(x, y) == HILL_TOP_RIGHT) setBlock(x, y, HILL_TOP_RIGHT_IN);
							}
						}
					}
				}
			}
		}
		
		return length;
	}
	
	private int buildJump(int xo, int maxLength) {
		gaps++;
		
		//jl: jump length
		//js: the number of blocks that are available at either side for free
		int js = random.nextInt(4) + 2;
		int jl = random.nextInt(2) + 2;
		
		int length = js * 2 + jl;
		
		boolean hasStairs = random.nextInt(3) == 0;
		
		int floor = height - 1 - random.nextInt(4);
		
		//run from the start x position, for the whole length
		for (int x = xo; x < xo + length; x++) {
			if (x < xo + js || x > xo + length - js - 1) {
				//run for all y's since we need to paint blocks upward
				for (int y = 0; y < height; y++) {	//paint ground up until the floor
					if (y >= floor) {
						setBlock(x, y, GROUND);
					} else if (hasStairs) { //if it is above ground, start making stairs of rocks
						//LEFT SIDE
						if (x < xo + js) {
							//we need to max it out and level because it wont
							//paint ground correctly unless two bricks are side by side
							if (y >= floor - (x - xo) + 1) {
								setBlock(x, y, ROCK);
							}
						} else { //RIGHT SIDE
							if (y >= floor - ((xo + length) - x) + 2) {
								setBlock(x, y, ROCK);
							}
						}
					}
				}
			}
		}
		
		return length;
	}
	
	private int buildTubes(int xo, int maxLength) {
		int length = random.nextInt(10) + 5;
		if (length > maxLength) length = maxLength;

		int floor = height - 1 - random.nextInt(4)
		;
		int xTube = xo + 1 + random.nextInt(4);
		int tubeHeight = floor - random.nextInt(2) - 2;
		
		for (int x = xo; x < xo + length; x++) {
			if (x > xTube + 1) {
				xTube += 3 + random.nextInt(4);
				tubeHeight = floor - random.nextInt(2) - 2;
			}
			
			if (xTube >= xo + length - 2) xTube += 10;
			
			if (x == xTube && random.nextInt(11) < difficulty + 1) {
				setSpriteTemplate(x, tubeHeight, new SpriteTemplate(Enemy.ENEMY_FLOWER, false));
				ENEMIES++;
			}

			for (int y = 0; y < height; y++) {
				if (y >= floor) {
					setBlock(x, y,GROUND);

				} else {
					if ((x == xTube || x == xTube + 1) && y >= tubeHeight) {
						int xPic = 10 + x - xTube;

						if (y == tubeHeight) {
							//tube top
							setBlock(x, y, (byte) (xPic + 0 * 16));
						} else {
							//tube side
							setBlock(x, y, (byte) (xPic + 1 * 16));
						}
					}
				}
			}
		}
		
		return length;
	}
	
	
	/*-------------------
	 *-TECHNICAL JAVA BS-
	 *-------------------*/
	
	
	public RandomLevel clone() throws CloneNotSupportedException {
		RandomLevel clone=new RandomLevel(width, height);
		
		clone.xExit = xExit;
		clone.yExit = yExit;
		byte[][] map = getMap();
		SpriteTemplate[][] st = getSpriteTemplate();
		
		for (int i = 0; i < map.length; i++) {
			for (int j = 0; j < map[i].length; j++) {
				clone.setBlock(i, j, map[i][j]);
				clone.setSpriteTemplate(i, j, st[i][j]);
			}
		}
		
		clone.BLOCKS_COINS = BLOCKS_COINS;
		clone.BLOCKS_EMPTY = BLOCKS_EMPTY;
		clone.BLOCKS_POWER = BLOCKS_POWER;
		clone.ENEMIES = ENEMIES;
		clone.COINS = COINS;
		
		return clone;
	}
}